#include "main.h"
#include <unistd.h>

/* Global file descriptor for UART communication interface */
/* Initialized to -1 to indicate unopened state for safety checking */
int 	UART_FD = -1;

/**
 * @brief Configure serial port communication parameters and operational settings
 * This comprehensive function sets up all UART communication parameters including
 * baud rate, data bits, parity, stop bits, and flow control configurations
 * @param fd: File descriptor for the opened serial port device
 * @param baudRate: Communication speed in bits per second (2400, 4800, 9600, 115200)
 * @param wordlength: Number of data bits per character (7 or 8 bits)
 * @param parity: Parity checking configuration ('o'=odd, 'e'=even, 'n'=none)
 * @param stopBit: Number of stop bits framing each character (1 or 2 bits)
 * @return uint8_t Returns TRUE if configuration successful, FALSE if any parameter invalid
 */
static uint8_t set_opt(int fd, uint32_t baudRate, uint8_t wordlength, char parity, uint8_t stopBit)
{
	/* Termios structure for storing and manipulating serial port attributes */
	struct termios opt;

	/* Retrieve current serial port attributes from system for modification */
	/* This populates the termios structure with existing port configuration */
	if (tcgetattr(fd, &opt) != 0)
	{
		printf("serial port error!\n");
		return FALSE;
	}

	/* Configure serial port for local connection and enable data reception */
	/* CLOCAL prevents modem control lines from affecting port operation */
	/* CREAD enables the receiver allowing data to be received on the port */
	opt.c_cflag |= CLOCAL | CREAD;

	/* Baud rate configuration section with support for common serial speeds */
	/* Sets both input and output baud rates to ensure bidirectional compatibility */
	switch (baudRate)
	{
	case 2400:
		/* Configure for 2400 baud communication speed */
		cfsetispeed(&opt, B2400);
		cfsetospeed(&opt, B2400);
		break;
	case 4800:
		/* Configure for 4800 baud communication speed */
		cfsetispeed(&opt, B4800);
		cfsetospeed(&opt, B4800);
		break;
	case 9600:
		/* Configure for 9600 baud communication speed - common default rate */
		cfsetispeed(&opt, B9600);
		cfsetospeed(&opt, B9600);
		break;
	case 115200:
		/* Configure for 115200 baud communication speed - high speed rate */
		cfsetispeed(&opt, B115200);
		cfsetospeed(&opt, B115200);
		break;
	default:
		/* Fallback to 9600 baud if unsupported baud rate specified */
		cfsetispeed(&opt, B9600);
		cfsetospeed(&opt, B9600);
		break;
	}

	/* Clear existing character size mask before setting new data bit configuration */
	/* This ensures clean state for character size bit manipulation */
	opt.c_cflag &= ~CSIZE;

	/* Data bits configuration supporting 7-bit and 8-bit character lengths */
	/* CS7 and CS8 constants define the number of data bits per character */
	switch (wordlength)
	{
	case 7:
		/* Configure for 7 data bits per character communication */
		opt.c_cflag |= CS7;
		break;
	case 8:
		/* Configure for 8 data bits per character communication - most common setting */
		opt.c_cflag |= CS8;
		break;
	}

	/* Parity configuration supporting odd, even, and no parity checking options */
	/* Parity bits provide basic error detection for serial communication */
	switch (parity)
	{
	/* Odd parity configuration where parity bit ensures odd number of 1-bits */
	case 'o':
	case 'O':
		opt.c_cflag |= PARENB;			 // Enable parity generation and checking
		opt.c_iflag |= (INPCK | ISTRIP); // Enable input parity checking and strip parity bits
		opt.c_cflag |= PARODD;			 // Use odd parity calculation instead of even
		break;
	/* Even parity configuration where parity bit ensures even number of 1-bits */
	case 'e':
	case 'E':
		opt.c_cflag |= PARENB;			 // Enable parity generation and checking
		opt.c_iflag |= (INPCK | ISTRIP); // Enable input parity checking and strip parity bits
		opt.c_cflag &= ~PARODD;		 // Use even parity calculation (clear odd parity flag)

		break;
	/* No parity configuration disabling all parity generation and checking */
	case 'n':
	case 'N':
		opt.c_cflag &= ~PARENB;						// Disable parity generation and checking
		opt.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); /* Disable canonical mode, echo, and signal handling */
		opt.c_oflag &= ~OPOST;							/* Disable output processing for raw mode */
		break;
	}

	/* Stop bits configuration controlling the framing bits after each character */
	/* Stop bits provide synchronization between characters in asynchronous communication */
	switch (stopBit)
	{
	case 1:
		/* Configure for 1 stop bit per character - most common setting */
		opt.c_cflag &= ~CSTOPB;
		break;
	case 2:
		/* Configure for 2 stop bits per character - provides extra synchronization */
		opt.c_cflag |= CSTOPB;
		break;
	}
	
	/* Non-blocking read configuration with immediate return characteristics */
	/* VTIME=0 and VMIN=0 creates non-blocking reads that return immediately */
	opt.c_cc[VTIME] = 0;  // Timeout in deciseconds between characters
	opt.c_cc[VMIN] = 0;   // Minimum characters to read before returning

	/* Advanced serial port configuration flags for optimized communication */
	/* These flags disable various modem controls and input/output processing */
	opt.c_cflag &= ~HUPCL;   // Disable hangup on close to maintain port state
	opt.c_iflag &= ~INPCK;   // Disable input parity checking for raw mode
	opt.c_iflag |= IGNBRK;   // Ignore break conditions on input
	opt.c_iflag &= ~ICRNL;   // Disable carriage return to newline translation
	opt.c_iflag &= ~IXON;    // Disable software flow control (XON/XOFF) on output
	opt.c_lflag &= ~IEXTEN;  // Disable extended input processing
	opt.c_lflag &= ~ECHOK;   // Disable echo of kill character
	opt.c_lflag &= ~ECHOCTL; // Disable echo of control characters
	opt.c_lflag &= ~ECHOKE;  // Disable kill character echo
	opt.c_oflag &= ~ONLCR;   // Disable newline to carriage return translation on output

	/* Flush all pending input and output data from serial buffers */
	/* TCIFLUSH discards received but unread data */
	/* TCIOFLUSH discards both received and transmitted but unread data */
	tcflush(fd, TCIFLUSH);
	tcflush(fd, TCIOFLUSH);

	/* Apply the configured serial port attributes immediately to the device */
	/* TCSANOW flag ensures changes take effect without waiting for completion */
	if ((tcsetattr(fd, TCSANOW, &opt)) != 0)
	{
		printf("serial set error!\n");
		return FALSE;
	}

	return TRUE;
}

/**
  * @brief  Initialize UART communication interface with specified parameters
  * This function opens the serial port device and configures all communication
  * parameters for reliable data transmission and reception
  * @param  uart_port: String path to serial port device (e.g., "/dev/ttyS0")
  * @param  baudRate: Communication speed in bits per second
  * @param  wordlength: Number of data bits per character (7 or 8)
  * @param  parity: Parity checking mode ('o', 'e', or 'n')
  * @param  stopBit: Number of stop bits (1 or 2)
  * @retval Success return TRUE if initialization successful, FALSE if any error occurs
  */
uint8_t uart_init(char *uart_port, uint32_t baudRate, uint8_t wordlength, char parity, uint8_t stopBit)
{
	/* Open serial port device with read/write access and non-blocking flags */
	/* O_RDWR enables both reading and writing to the serial port */
	/* O_NOCTTY prevents the port from becoming the controlling terminal */
	/* O_NDELAY enables non-blocking mode for the serial port operations */
	UART_FD = open(uart_port, O_RDWR | O_NOCTTY | O_NDELAY);

	/* Verify serial port opening was successful before proceeding with configuration */
	if ( UART_FD == -1)
	{
		printf("open_port: Unable to open: %s\n", uart_port);
		return FALSE;
	}
	else
	{
		/* Apply serial port configuration parameters to the opened device */
		/* This sets baud rate, data bits, parity, and stop bits as specified */
		if (set_opt(UART_FD, baudRate, wordlength, parity, stopBit) != TRUE)
		{
			printf("open_port: set baud rate failed!\n");
			return FALSE;
		}

	}
	
	return TRUE;
}

/**
 * @brief Close and cleanup UART communication interface
 * This function properly closes the serial port file descriptor
 * and releases system resources associated with the UART connection
 * @return uint8_t Returns TRUE if closure successful, FALSE if error occurs
 */
uint8_t uart_destory(void)
{
	/* Close the serial port file descriptor and check for errors */
	if (close(UART_FD) < 0)
	{
		return FALSE;
	}

	return TRUE;
}

/**
 * @brief Receive data from the serial port interface
 * This function reads incoming data from the UART buffer into the provided
 * memory location with non-blocking behavior due to O_NDELAY flag
 * @param buffer: Pointer to memory buffer for storing received data
 * @param len: Maximum number of bytes to read from the serial port
 * @return uint16_t Returns number of bytes actually read, 0 if no data available
 */
uint16_t serial_recv(uint8_t *buffer, uint16_t len)
{
	/* Variable to store the actual number of bytes read from serial port */
	uint16_t read_num = 0;

	/* Validate input parameters to ensure safe buffer operations */
	if ((len) && (buffer != NULL))
	{
		/* Perform non-blocking read operation from serial port */
		/* Returns immediately with available data or 0 if no data */
		read_num = read(UART_FD, buffer, len);		
	}

	return read_num;
}

/**
 * @brief Transmit data through the serial port interface
 * This function sends data from the provided buffer through the UART
 * interface for transmission to the connected serial device
 * @param buffer: Pointer to memory buffer containing data to transmit
 * @param len: Number of bytes to transmit through the serial port
 * @return uint16_t Returns number of bytes actually written, 0 if error occurs
 */
uint16_t serial_send(uint8_t *buffer, uint16_t len)
{
	/* Variable to store the actual number of bytes written to serial port */
	uint16_t write_num = 0;

	/* Validate input parameters to ensure safe buffer operations */
	if ((len) && (buffer != NULL))
	{
		/* Perform write operation to serial port for data transmission */
		write_num = write(UART_FD, buffer, len);
	}

	return write_num;
}